import sys
import time
from datetime import datetime

import pytz
from PyQt5 import QtCore
from PyQt5.QtCore import QTimer, pyqtSignal, Qt
from PyQt5.QtWidgets import QDialog, QApplication

from serialPort import cm3_pb2
from utils.logRecorders import LogRecorder
from loguru import logger
from serialPort.serialPortUI import Ui_SerialPortDialog
import serial
import serial.tools.list_ports


def PromptForDisplay(display, dictData):
    # dictData = eval(strData)
    if type(dictData['msg_type']) != type(cm3_pb2.Input):
        expr = f"display.msg_type = cm3_pb2.{dictData['msg_type']}"
        exec(expr)
    else:
        display.msg_type = dictData['msg_type']
    try:
        display.input.key = dictData['input']['key']
        display.input.rot = dictData['input']['rot']
    except Exception as e:
        logger.debug(e)
    try:
        display.item.index = dictData['item']['index']
        display.item.result = dictData['item']['result']
    except Exception as e:
        logger.debug(e)
    try:
        display.item.all_pass = dictData['item']['all_pass']
    except Exception as e:
        logger.debug(e)
    try:
        display.image.index = dictData['image']['index']
    except Exception as e:
        logger.debug(e)
    try:
        display.status.onoff = dictData['status']['onoff']
        display.status.light = dictData['status']['light']
        display.status.module_temp = dictData['status']['module_temp']
        display.status.voltage = dictData['status']['voltage']
        display.status.current = dictData['status']['current']
        display.status.power = dictData['status']['power']
        display.status.pcb_temp = dictData['status']['pcb_temp']
    except Exception as e:
        logger.debug(e)
    try:
        display.status.gyro = dictData['status']['gyro']
    except Exception as e:
        logger.debug(e)
    try:
        display.version.hw = dictData['version']['hw']
        display.version.sw = dictData['version']['sw']
    except Exception as e:
        logger.debug(e)
    try:
        display.version.boot = dictData['version']['boot']
    except Exception as e:
        logger.debug(e)
    try:
        display.dtc.prefix = dictData['dtc']['prefix']
        display.dtc.value = dictData['dtc']['value']
    except Exception as e:
        logger.debug(e)
    try:
        display.SingIn = dictData['SingIn']
    except Exception as e:
        logger.debug(e)
    display.points.count = dictData['points']['count']
    display.points.list = dictData['points']['list']


# qmut_1 = QMutex() # 创建线程锁

# 继承QThread
class Runthread(QtCore.QThread):
    #  通过类成员对象定义信号对象
    _signal = pyqtSignal(object)
    # 串口对象
    ser = serial.Serial()

    def __init__(self):
        super(Runthread, self).__init__()

    def __del__(self):
        self.wait()

    def run(self):
        # qmut_1.lock()  # 加锁
        logger.info('Runthread1')
        time.sleep(6)
        try:
            logger.info('Runthread2')
            # Linux open 操作总会卡几秒
            self.ser.open()
            logger.info('Runthread3')
            if self.ser.isOpen():
                logger.info('Runthread4')
                self.ser.flushInput()
                logger.info('Runthread5')
                # self.ser.close()
                # print('mySerial.ser.close')
                # 此后卡一下
                # time.sleep(0.2)
                self._signal.emit(self.ser)  # 注意这里与_signal = pyqtSignal(str)中的类型相同
                # qmut_1.unlock()  # 解锁
            # print('++++MyThread++++')
        except Exception as e:
            logger.info(e)


class SerialPortWindow(Ui_SerialPortDialog, QDialog):
    serialPortSignal = pyqtSignal(dict)  # 子界面类创建信号用来绑定主界面类的函数方法
    data_num_received = 0
    data_num_sended = 0

    def __init__(self):
        super().__init__()
        self.setupUi(self)
        # 串口对象
        self.mySerial = serial.Serial()
        # 串口检测按钮
        self.btnSerialWatch.clicked.connect(self.port_check)
        self.autoLinkSerial.stateChanged.connect(self.port_check)
        # 串口信息显示
        # self.selSerialPort.currentTextChanged.connect(self.port_imf)
        # 关闭串口按钮
        self.btnSerialOpenClose.clicked.connect(self.btnOpenClosePort)
        # 定时器接收数据
        self.timer_recv = QTimer(self)
        self.timer_recv.timeout.connect(self.data_receive)
        # 定时扫描串口，周期为 300ms
        self.timer_port = QTimer()
        self.timer_port.timeout.connect(self.port_check)
        self.timer_port.start(300)
        # 接收数据和发送数据数目置零
        self.data_num_received = 0
        self.textReceNum.setText(str(self.data_num_received))
        self.data_num_sended = 0
        self.textSendNum.setText(str(self.data_num_sended))

        self.threadFlag = False  # 初始化线程
        # 定时监控监测串口连接状态，周期为 1000ms
        self.checkSerialTimer = QTimer()
        self.checkSerialTimer.timeout.connect(self.CheckSerialFun)
        self.checkSerialTimer.start(1000)
        self.checkSerialStatus.stateChanged.connect(self.CheckSerialFun)
        self.reConnect = False
        # 初始化日志记录
        ## 加载数据库
        timeStr = datetime.now(pytz.timezone('Asia/Shanghai')).strftime("touchLog_%m-%d_%H-%M")
        logRecorder = LogRecorder()
        logRecorder.InitLogger(log_path='./data/logs/', log_name=f'{timeStr}_BoevxaLog.log')

    def CheckSerialFun(self):
        self.checkSerialTimer.stop()
        serialStatus = True
        if self.checkSerialStatus.isChecked():
            if not self.mySerial.isOpen():
                serialStatus = False
            else:
                try:
                    a = self.mySerial.out_waiting
                    a += 1
                except Exception as e:
                    serialStatus = False
                    logger.error(e)
            if (not serialStatus) and self.autoLinkSerial.isChecked():
                self.serialPortSignal.emit({"data": "串口已断开", "type": 'statusbar'})
                logger.error("串口已断开！！尝试重新连接~~~~~~")
                self.port_check()
                self.reConnect = True
            if self.reConnect and self.autoLinkSerial.isChecked():
                if self.mySerial.isOpen():
                    self.reConnect = False
                    logger.success("串口中断，重新连接串口 成功 ✅")
                    self.serialPortSignal.emit({"data": "串口中断，重新连接串口 成功 ✅", "type": 'statusbar'})
                else:
                    logger.error("串口中断，重新连接串口失败 ❎")
                    self.serialPortSignal.emit({"data": "串口中断，重新连接串口失败 ❎", "type": 'statusbar'})
        self.checkSerialTimer.start(1000)

    # 接收来自主界面的数据
    # {"data":XX,"type":0,}
    # type : send_ 往串口发
    def RecvMainData(self, dictData):
        typeValue = dictData.get("type", None)
        # dataStr = dictData.get('data', '')
        # 接受Form1传过来的msg，保存到自己的变量里面。
        # print('来自主界面信息：{}，type: {}'.format(dataStr, typeValue))
        if typeValue == 'send_protobuf':
            self.data_send(data=dictData.get('data', ''), data_type='protobuf')
        if '_to_' in typeValue:
            self.serialPortSignal.emit({"data": dictData.get('data', ''), "type": typeValue, })

    def ClosePortFun(self):
        # print('function OpenClosePort CLOSE')
        self.timer_recv.stop()
        try:
            self.mySerial.close()
        except:
            pass
        self.serialPortSignal.emit({"data": "串口已断开", "type": 'statusbar'})
        # self.btnSerialOpenClose.setEnabled(True)
        self.btnSerialOpenClose.setText("打开串口")
        # 接收数据和发送数据数目置零
        self.serialReceNum = 0
        self.textReceNum.setText(str(self.serialReceNum))
        self.serialSendNum = 0
        self.textSendNum.setText(str(self.serialSendNum))
        self.state_label.clear()

    # 串口检测
    def port_check(self):
        # print('function port_check', self.mySerial.isOpen())
        # QTime貌似只能stop不能kill 不停止重新调用就会 加快
        self.timer_port.stop()
        if self.autoLinkSerial.checkState():
            self.timer_port.start(300)
        # 检测所有存在的串口，将信息存储在字典中
        self.Com_Dict = {}
        port_list = list(serial.tools.list_ports.comports())
        self.selSerialPort.clear()
        for port in port_list:
            self.Com_Dict["%s" % port[0]] = "%s" % port[1]
            self.selSerialPort.addItem(port[0])
            if 'ttyAMA' in self.selSerialPort.currentText().strip():
                self.threadFlag = False
                continue
            if not self.mySerial.isOpen():
                if len(self.selSerialPort.currentText().strip()) > 0:
                    # print('++++++++', 'self.OpenPortFun()')
                    self.mySerial.port = self.selSerialPort.currentText()
                    self.mySerial.baudrate = int(self.selSerialBaudRate.currentText())
                    self.mySerial.bytesize = int(self.selSerialDataBit.currentText())
                    self.mySerial.stopbits = int(self.selSerialStopBit.currentText())
                    self.mySerial.parity = self.selSerialParityBit.currentText()
                    if self.threadFlag == False and self.autoLinkSerial.checkState():
                        # 创建线程
                        self.thread = Runthread()
                        # 连接信号
                        self.thread._signal.connect(self.OpenPortFun)  # 进程连接回传到GUI的事件
                        self.thread.ser = self.mySerial
                        # 开始线程
                        self.thread.start()
                        self.threadFlag = True
                        self.serialPortSignal.emit({"data": "正在检测串口", "type": 'statusbar', })
                self.OpenPortFun('tt')
        if (len(self.Com_Dict) == 0) or ('ttyAMA' in self.selSerialPort.currentText().strip()):
            self.state_label.setText("无串口")
            self.serialPortSignal.emit({"data": "未检测到设备", "type": 'statusbar'})
            logger.error("未检测到设备")
            if self.mySerial.isOpen():
                self.ClosePortFun()
            else:
                self.threadFlag = False

    # 串口信息
    def port_imf(self):
        # print('function port_imf')
        # 显示选定的串口的详细信息
        imf_s = self.selSerialPort.currentText()
        if imf_s != "":
            self.state_label.setText(self.Com_Dict[self.selSerialPort.currentText()])

    # 打开/关闭串口
    def btnOpenClosePort(self):
        if self.btnSerialOpenClose.text() == "打开串口" and self.selSerialPort.currentText() != '':
            # print('function OpenClosePort OPEN')
            self.mySerial.port = self.selSerialPort.currentText()
            self.mySerial.baudrate = int(self.selSerialBaudRate.currentText())
            self.mySerial.bytesize = int(self.selSerialDataBit.currentText())
            self.mySerial.stopbits = int(self.selSerialStopBit.currentText())
            self.mySerial.parity = self.selSerialParityBit.currentText()
            try:
                self.mySerial.open()
            except Exception as e:
                logger.debug(e)
                # msg_box = QMessageBox(QMessageBox.Critical, 'Port Error', '此串口不能被打开！')
                # msg_box.exec_()
                self.serialPortSignal.emit({"data": "此串口不能被打开", "type": 'statusbar'})
                logger.error('此串口不能被打开！')
                return None
            if self.mySerial.isOpen():
                # print('self.mySerial.isOpen')
                self.autoLinkSerial.setChecked(True)
                self.checkSerialStatus.setChecked(True)
                logger.info("已连接串口")
                self.serialPortSignal.emit({"data": "已连接串口", "type": 'statusbar'})
                self.btnSerialOpenClose.setText("关闭串口")
                # 打开串口接收定时器，周期为2ms
                self.timer_recv.start(2)
                self.port_imf()
        else:
            self.ClosePortFun()

    def OpenPortFun(self, serObj):
        if isinstance(serObj, serial.Serial):
            self.mySerial = serObj
            # print(self.mySerial)
        if self.mySerial.isOpen():
            self.autoLinkSerial.setChecked(True)
            self.checkSerialStatus.setChecked(True)
            # print('self.mySerial.isOpen')
            logger.info("已连接串口")
            self.serialPortSignal.emit({"data": "已连接串口", "type": 'statusbar'})
            self.btnSerialOpenClose.setText("关闭串口")
            # 打开串口接收定时器，周期为2ms
            self.timer_recv.stop()
            self.timer_recv.start(2)
            self.port_imf()
            self.threadFlag = False
        else:
            self.ClosePortFun()

    # 接收数据
    def data_receive(self):
        # print('function data_receive')
        try:
            num = self.mySerial.inWaiting()
        except:
            self.timer_recv.stop()
            # print('elf.timer_recv.stop()')
            return None
        if num > 0:
            data = self.mySerial.read(num)
            # print('-' * 20, data)
            num = len(data)
            # protobuf模式
            if self.protobufReceive.checkState():
                # print('收到 protobuf 数据：{}'.format(data))
                display = cm3_pb2.cm3Display()
                try:
                    display.ParseFromString(data)
                except Exception as err:
                    print(err)
                # dataStr = 'ListDisplay:{},{},{},{},{},{},{}'.format(display.id, display.input.key, display.input.rot,
                #                                                     display.status.temp, display.status.light,
                #                                                     display.version.hw, display.version.sw)
                # 指定Tokyo时区当前时间
                # timeStr = datetime.now(pytz.timezone('Asia/Shanghai')).strftime("%Y-%m-%d %H:%M:%S.%f")
                # print(display)
                self.serialPortSignal.emit({"data": display, "type": 'rece_protobuf', })
            # hex显示
            elif self.hexReceive.checkState():
                out_s = ''
                for i in range(0, len(data)):
                    out_s = out_s + '{:02X}'.format(data[i]) + ' '
                # self.s2__receive_text.insertPlainText(out_s)
                # self.s2__receive_text_2.insertPlainText("收到 hex 数据 \r\n")
                # print('收到 hex 数据：{}'.format(out_s))
                # 子界面通过signal_2 向主界面传递数据
                self.serialPortSignal.emit({"data": out_s, "type": 'rece_hex', })
            else:
                # 串口接收到的字符串为b'123',要转化成unicode字符串才能输出到窗口中去
                dataStr = data.decode('iso-8859-1')
                # self.s2__receive_text.insertPlainText(dataStr)
                # self.s2__receive_text_2.insertPlainText("收到 ASCII 数据 \r\n")
                # print('收到 ASCII 数据：{}'.format(dataStr))
                self.serialPortSignal.emit({"data": dataStr, "type": 'rece_ascii', })

            # 统计接收字符的数量
            self.data_num_received += num
            self.textReceNum.setText(str(self.data_num_received))

    # 发送数据
    def data_send(self, data, data_type=''):
        sendType = 'ascii'
        if len(data_type.strip()) > 1:
            if 'protobuf' in data_type:
                sendType = 'protobuf'
            elif 'hex' in data_type:
                sendType = 'hex'
        else:
            if self.protobufSend.isChecked():
                sendType = 'protobuf'
            elif self.hexSend.isChecked():
                sendType = 'hex'
        if self.mySerial.isOpen():
            input_s = data
            if input_s != "":
                # 非空字符串
                if 'protobuf' in sendType:
                    display = cm3_pb2.cm3Display()
                    try:
                        # 必须传到函数里面去赋值，直接赋值会报错？？
                        PromptForDisplay(display, input_s)
                    except Exception as e:
                        pass
                    input_s = display.SerializeToString()
                elif 'hex' in sendType:
                    # hex发送
                    input_s = input_s.strip()
                    send_list = []
                    while input_s != '':
                        try:
                            num = int(input_s[0:2], 16)
                        except ValueError:
                            # QMessageBox.critical(self, 'wrong data', '请输入十六进制数据，以空格分开!')
                            return None
                        input_s = input_s[2:].strip()
                        send_list.append(num)
                    input_s = bytes(send_list)
                else:
                    # ascii发送
                    input_s = (input_s + '\r\n').encode('utf-8')
                num = self.mySerial.write(input_s)
                # print('-' * 10, num)
                self.data_num_sended += num
                self.textSendNum.setText(str(self.data_num_sended))
        else:
            logger.error("串口已断开！！")
            # self.btnSerialOpenClose.setEnabled(True)
            # self.btnSerialOpenClose.setText("打开串口")
            # self.btnSerialOpenClose.click()
            # msg_box = QMessageBox(QMessageBox.Information, '信息提示', '串口重新打开，请重试')
            # msg_box.exec_()
            # 最后的Yes表示弹框的按钮显示为Yes，默认按钮显示为OK,不填QMessageBox.Yes即为默认
            # QMessageBox.information(self, "信息提示", "串口已刷新，请重试", QMessageBox.Ok)
            # print("串口重新打开，请重试")


# 程序入口
if __name__ == "__main__":
    # 适应高DPI设备
    QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
    # 解决图片在不同分辨率显示模糊问题
    QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)
    app = QApplication(sys.argv)
    # myWin = MyMainWindow()
    SerialPortWin = SerialPortWindow()
    SerialPortWin.show()
    sys.exit(app.exec_())
